export interface ICropOption {
  quality?: number;
  width?: number;
  height?: number;
  url?: string;
}

export const VIDEO_TIME_DECIMAL = 2;

export async function getFrameImage(
  video: string | File | HTMLVideoElement,
  currentTime = 0,
  type: 'base64' | 'file' = 'base64',
  options?: ICropOption,
): Promise<string | Blob> {
  let videoElement: HTMLVideoElement;
  let url: string;
  if (video instanceof HTMLVideoElement) {
    videoElement = video;
    url = video.src;
  } else {
    let videoFile: Blob;
    url = typeof video === 'string' ? video : window.URL.createObjectURL(video);
    if (typeof video === 'string') {
      const response = await fetch(video);
      videoFile = await response.blob();
    } else {
      videoFile = video;
    }

    const videoUrl = window.URL.createObjectURL(videoFile);
    videoElement = document.createElement('video');
    videoElement.crossOrigin = 'anonymous';
    videoElement.preload = 'auto';
    videoElement.muted = true;
    videoElement.src = videoUrl;
  }

  const result = captureVideo(videoElement, currentTime, type, {
    ...options,
    url,
  });

  result.finally(() => {
    if (video instanceof File) {
      URL.revokeObjectURL(url); //释放url
    }
    if (!(video instanceof HTMLVideoElement)) {
      videoElement.remove();
    }
  });

  return result;
}

export async function captureVideo(
  videoElement: HTMLVideoElement,
  currentTime: number,
  type: 'base64' | 'file' = 'base64',
  options?: ICropOption,
): Promise<string | Blob> {
  const { quality = 1, width, height } = options || {};
  const element = videoElement;

  if (element.readyState < HTMLMediaElement.HAVE_METADATA) {
    await new Promise((resolve) => {
      element.oncanplay = resolve;
    });
  }
  // eslint-disable-next-line require-atomic-updates
  element.currentTime = +currentTime.toFixed(VIDEO_TIME_DECIMAL);
  await new Promise((resolve) => {
    element.onseeked = resolve;
  });
  await waitUntilSeak(element);

  const canvas = document.createElement('canvas');
  const { videoWidth, videoHeight } = element;
  let dw = videoWidth;
  let dh = videoHeight;
  if (width && height) {
    dw = width;
    dh = height;
  } else if (width) {
    dw = width;
    dh = (videoHeight * width) / videoWidth;
  } else if (height) {
    dh = height;
    dw = (videoWidth * height) / videoHeight;
  }

  canvas.width = dw;
  canvas.height = dh;
  const ctx = canvas.getContext('2d');
  ctx!.imageSmoothingEnabled = false;
  ctx!.drawImage(element, 0, 0, videoWidth, videoHeight, 0, 0, dw, dh);

  const base64 = canvas.toDataURL('image/png', quality);
  if (type === 'base64') {
    return base64;
  } else {
    return base64ToBlob(base64, 'image/png');
  }
}

async function waitUntilSeak(video: HTMLVideoElement) {
  let lastTime = -1;
  for (let i = 0; i < 10; i++) {
    await new Promise((resolve) => requestAnimationFrame(resolve));
    const { currentTime } = video;
    if (currentTime === lastTime) break;
    lastTime = currentTime;
  }
}

function base64ToBlob(base64Data: string, contentType = '', sliceSize = 512) {
  const byteCharacters = atob(base64Data.split(',')[1]);
  const byteArrays = [];

  for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
    const slice = byteCharacters.slice(offset, offset + sliceSize);

    const byteNumbers = new Array(slice.length);
    for (let i = 0; i < slice.length; i++) {
      byteNumbers[i] = slice.charCodeAt(i);
    }

    const byteArray = new Uint8Array(byteNumbers);
    byteArrays.push(byteArray);
  }

  return new Blob(byteArrays, { type: contentType });
}
